package com.gitee.qdbp.general.thirdpart.biz.wechat.service;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.commons.codec.digest.DigestUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import com.gitee.qdbp.able.exception.ServiceException;
import com.gitee.qdbp.general.thirdpart.api.trade.model.PrepayData;
import com.gitee.qdbp.general.thirdpart.biz.PayUtils;
import com.gitee.qdbp.general.thirdpart.error.PaymentErrorCode;
import com.gitee.qdbp.tools.files.ImageTools;
import com.gitee.qdbp.tools.utils.JsonTools;
import com.gitee.qdbp.tools.utils.VerifyTools;

public class WechatUtils {

    /** 日志对象 **/
    private static final Logger log = LoggerFactory.getLogger(WechatUtils.class);

    public static final String CHARSET = "UTF-8";

    /** 格式化回调参数 **/
    public static String formatPassbackParams(Map<String, Object> params) {
        if (params == null) {
            return null;
        }

        // 最大长度: 512
        // 公用回传参数，如果请求时传递了该参数，则返回给商户时会回传该参数。
        // 支付宝/微信会在异步通知时将该参数原样返回。本参数必须进行UrlEncode之后才可以发送给支付宝
        String string = PayUtils.mapToParams(params);
        try {
            return URLEncoder.encode(string, CHARSET);
        } catch (UnsupportedEncodingException e) {
            throw new IllegalStateException("UnsupportedEncoding: " + CHARSET);
        }
    }

    /** 解析回调参数 **/
    public static Map<String, String> parsePassbackParams(String string) {
        if (VerifyTools.isBlank(string)) {
            return new HashMap<>();
        }

        try {
            return PayUtils.paramsToMap(URLDecoder.decode(string, CHARSET));
        } catch (UnsupportedEncodingException e) {
            throw new IllegalStateException("UnsupportedEncoding: " + CHARSET);
        }
    }

    /** 创建二维码(用于扫码支付) **/
    public static String createQrCodeImage(String content, int size, int margin) {
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        ImageTools.generateQrCode(content, size, margin, os);
        byte[] bytes = os.toByteArray();
        return "data:image/png;base64," + Base64.getEncoder().encodeToString(bytes);
    }

    /** URL追加支付参数 **/
    public static String urlAppendParams(String url, String prepayId, PrepayData payment) {
        StringBuilder buffer = new StringBuilder();
        buffer.append(url);
        buffer.append(url.indexOf('?') >= 0 ? "&" : "?").append("prepayId=").append(prepayId);
        buffer.append("&orderId=").append(payment.getOrderId());
        buffer.append("&paymentId=").append(payment.getPaymentId());
        buffer.append("&orderType=").append(payment.getOrderType());
        buffer.append("&orderTitle=").append(payment.getOrderTitle());
        buffer.append("&orderContent=").append(payment.getOrderContent());
        buffer.append("&totalAmount=").append(payment.getTotalAmount());
        return buffer.toString();
    }

    /** 构造签名 **/
    public static String generateSignature(Map<String, String> params, String encryptKey, boolean encode,
            String... ignores) {
        // https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3

        Set<String> keysSet = params.keySet();
        Object[] keys = keysSet.toArray();
        Arrays.sort(keys);
        StringBuilder buffer = new StringBuilder();
        for (Object key : keys) {
            if (ignores.length > 0 && PayUtils.inArray(key, ignores)) {
                continue;
            }
            String value = params.get(key);
            if (value == null || value.length() == 0) {
                continue;
            }
            buffer.append(buffer.length() > 0 ? "&" : "").append(key).append("=");
            if (encode) {
                try {
                    value = URLEncoder.encode(value, CHARSET);
                } catch (UnsupportedEncodingException ignore) {
                }
            }
            buffer.append(value);
        }
        buffer.append("&key=").append(encryptKey);
        String sign = DigestUtils.md5Hex(buffer.toString()).toUpperCase();
        if (log.isTraceEnabled()) {
            log.trace("[WechatSignature]: \n{}\n{}", buffer, sign);
        }
        return sign;
    }

    /** 检查签名 **/
    public static void checkSignature(Map<String, String> params, String encryptKey, boolean encode, String signName)
            throws ServiceException {
        String oldsign = params.get(signName);
        String newsign = generateSignature(params, encryptKey, encode, signName);
        if (!oldsign.equalsIgnoreCase(newsign)) {
            if (log.isWarnEnabled()) {
                log.warn("Failed to check wechat signature. {}", JsonTools.newlineLogs(newsign, params));
            }
            throw new ServiceException(PaymentErrorCode.PAYMENT_SIGNATURE_CHECK_FAILED);
        }
    }

    /** 将map转换为xml **/
    public static String toXml(Map<String, String> map) {
        StringBuilder buffer = new StringBuilder();
        buffer.append("<xml>");

        for (Entry<String, String> entry : map.entrySet()) {
            buffer.append("<").append(entry.getKey()).append(">");
            buffer.append(entry.getValue());
            buffer.append("</").append(entry.getKey()).append(">");
        }

        buffer.append("</xml>");
        return buffer.toString();
    }

    /** 将xml解析为map **/
    public static Map<String, String> doXmlParse(String xml) throws ServiceException {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        Document document;
        try {
            DocumentBuilder builder = factory.newDocumentBuilder();
            InputStream is = new ByteArrayInputStream(xml.getBytes(CHARSET));
            document = builder.parse(is);
        } catch (Exception e) {
            log.warn("[WechatPay]XmlParseError.\n\t{}", xml, e);
            throw new ServiceException(PaymentErrorCode.PAYMENT_HANDLE_ERROR);
        }

        // 得到xml根元素
        Element root = document.getDocumentElement();
        // 得到根元素的所有子节点
        NodeList elements = root.getChildNodes();

        // 遍历所有子节点
        Map<String, String> map = new HashMap<>();
        for (int i = 0; i < elements.getLength(); i++) {
            Node e = elements.item(i);
            if (e.getNodeType() != Node.ELEMENT_NODE) {
                continue;
            }
            NodeList children = e.getChildNodes();
            if (children.getLength() > 0) {
                for (int j = 0; j < elements.getLength(); j++) {
                    Node c = elements.item(j);
                    if (c.getNodeType() != Node.ELEMENT_NODE) {
                        continue;
                    }
                    map.put(c.getNodeName(), c.getTextContent());
                }
            } else {
                map.put(e.getNodeName(), e.getTextContent());
            }
        }
        return map;
    }

}
